from django.shortcuts import render,redirect,reverse
from django.views import View
from django import http
from django.db import DatabaseError    # 导入数据库捕获异常时，错误语法包
from django.contrib.auth import login,authenticate,logout,mixins   #导入django自带的login
from django_redis import get_redis_connection
import re,json
from django.conf import settings
from django.conf import global_settings
from random import randint

from celery_tasks.sms.tasks import send_sms_code
from .utils import generate_verify_email_url,check_token_to_user
from django.core.paginator import Paginator

from .models import Users,Address
from goods.models import SKU
from celery_tasks.email.tasks import send_verify_email
from meid_duo.utils.response_code import RETCODE   #导入自制状态码
import logging
from meid_duo.utils.views import LoginRequiredView
from carts.utils import merge_cart_cookie_to_redis
from orders.models import OrderInfo
from oauth.utils import generate_openid_signature,check_openid_sign

# 创建日志输出器
logger = logging.getLogger("django")


class Register(View):
    # 注册页面
    def get(self, request):
        return render(request, 'register.html')

    def post(self,request):
        """注册功能实现"""
        # 获取表单数据
        username = request.POST.get("username")
        password = request.POST.get("password")
        password2 = request.POST.get("password2")
        mobile = request.POST.get("mobile")
        sms_code = request.POST.get("sms_code")
        allow = request.POST.get("allow")

        # 数据校验              all()判断是否为空
        if not all([username, password, password2, mobile, sms_code, allow]):
            return http.HttpResponseForbidden("输入不能为空")
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$',username):
            return http.HttpResponseForbidden("请输入5-20个字符的用户名")
        if not re.match(r'^[0-9A-Za-z]{8,20}$',password):
            return http.HttpResponseForbidden("请输入8-20位的密码")
        if password != password2:
            return http.HttpResponseForbidden("两次密码输入不一样")
        if not re.match(r'^1[3456789]\d{9}$',mobile):
            return http.HttpResponseForbidden("您输入的手机号格式不正确")

        redis_conn = get_redis_connection("verify_code")
        sms_code_server = redis_conn.get('sms_%s' % mobile)

        if sms_code_server is None or sms_code != sms_code_server.decode():
            return http.HttpResponseForbidden("手机验证码有误")

        #  短信验证码校验后期再补充


        # 创建用户，写入数据库
        # 因为Users模型类为自定义，连接数据库时，创建时用create_user(等同于正常情况下使用create)
        try:
            user = Users.objects.create_user(
                username=username,
                password=password,  #使用create_user时，密码在存储时会自动加密
                mobile=mobile
            )
        except DatabaseError as info:
            logger.error(info)
            return render(request,'register.html',{'register_errmsg':'用户注册失败'})
            # vue使用，当捕获异常时，将错误信息填充到html中的模板语言去


        # 状态保持   需要导入django自带的login
        login(request,user)   #存储用户的id到session中记录它的登录状态
        response = redirect('/')
        # response = redirect(request.GET.get("next", '/'))
        # response = render(request,"index.html")
        # 重定向到首页
        response.set_cookie('username',username,max_age=3600 * 24 * 15)
        return response


class UsernameCountView(View):
    """检查用户名是否重名"""
    def get(self,request,username):
        # 数据库使用聚合函数时，count不需导入过滤器，其他几个需要
        count = Users.objects.filter(username=username).count()

        return http.JsonResponse({"code":RETCODE.OK,"errmsg":"OK","count":count})


class MobileCountView(View):
    """检查手机号是否重复"""
    def get(self,request,mobile):
        count = Users.objects.filter(mobile=mobile).count()
        return http.JsonResponse({"code": RETCODE.OK, "errmsg": "OK", "count": count})


class LoginView(View):
    def get(self,request):
        return render(request, "login.html")

    def post(self,request):
        username = request.POST.get("username")
        password = request.POST.get("password")
        remembered = request.POST.get("remembered")   # 是否勾选记住登录

        if not all([username,password]):
            return http.HttpResponseForbidden("缺少参数")
        # user = Users.objects.get(username=username)
        # user.check_password(password)      逻辑实现

        # django 认证自带方法authenticate
        # 它以credentials为关键字参数，默认为username和password，
        # 根据每个认证的后端进行检查，如果credentials对某个后端有效则返回一个User对象。
        # 如果credentials对任何后端都无效，或者如果后端引发了PermissionDenied，则返回None。

        # 自定义后端认证，实现多账号（手机号和用户名都可以）登录。ps：使用自定义后端认证，需要去settings更改配置
        user = authenticate(username=username, password=password)

        if user is None:
            return render(request,"login.html", {"account_errmsg":"用户名或密码错误"})

        # if remembered != 'on':
        #     settings.SESSION_COOKIE_AGE = 0   # 通过修改django全剧变量中设置的默认值实现
        # login(request, user)

        # 通过redis 设置session 实现
        login(request, user)  # 勾选记住登录，默认时间两周
        if remembered != "on":
            request.session.set_expiry(0)  # 未勾选记住登录，浏览器会话结束时，自动销毁cookies和session

        response = redirect(request.GET.get("next",'/'))    # 重定向到首页

        response.set_cookie('username',user.username, max_age=3600 * 24 * 15)  #设置cookies
        # 登录成功那一刻合并购物车
        merge_cart_cookie_to_redis(request,user,response)
        return response   #响应


class LogoutView(View):

    def get(self,request):
        """推出登录，注销缓存，重定向到登录界面"""
        logout(request)
        response = redirect("/login/")
        response.delete_cookie('username')
        return response


class UserInfoView(mixins.LoginRequiredMixin,View):  # 扩展类实现
    def get(self,request):
        """提供用户中心界面"""
        
        # 判断当前用户是否登录,如果登录返回用户中心界面
        # 如果用户没有登录,就重定义到登录
        # is_authenticated登录验证成功则返回True。反之，返回False。
        user = request.user
        if user.is_authenticated:
            return render(request,'user_center_info.html')
        else:
            return redirect("/login/?next=/info/")
        # return render(request,'user_center_info.html')


class EmailView(LoginRequiredView):
    """添加用户邮箱"""
    def put(self,request):
        """前端发送的是put请求"""
        # put 请求非表单数据，用request.body取得数据，此数据为bytes类型
        json_dict = json.loads(request.body.decode())  # 获得数据，json格式字典
        email = json_dict.get('email')

        if all([email]) is None:
            return http.HttpResponseForbidden('缺少参数')
        # 校验
        if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$',email):
            return http.HttpResponseForbidden('邮箱错误')
        # 获取user对象
        user = request.user
        user.email = email # 设置user对象email字段
        user.save()  # 保存


        # 获取激活邮箱的路由
        # 在此地还要发送一个邮件到email
        # from django.core.mail import send_mail
        # # send_mail(邮件主题, 普通邮件正文, 发件人邮箱, [收件人邮件], html_message='超文本邮件内容')
        # send_mail('美多', '', '美多商城<itcast99@163.com>', [email], html_message='收钱了')
        # verify_url = 'http://www.meiduo.site:8000/emails/verification/?token=2'
        verify_url = generate_verify_email_url(user)
        # 异步使用celery
        send_verify_email.delay(email,verify_url)

        return http.JsonResponse({"code":RETCODE.OK,"errmsg":"OK"})


class VerifyEmailView(View):
    """激活邮箱"""
    def get(self,request):
        # 获取toker
        token = request.GET.get("token")
        # 获取解密后的user对象
        user = check_token_to_user(token)
        if user is None:
            return http.HttpResponseForbidden("token无效")
        # 更改数据库中的email_active 字段
        user.email_active = True
        user.save()
        return redirect("/info/")  # 重定向到来的页面


class AddressView(LoginRequiredView):
    """收获地址页面"""
    def get(self,request):
        user = request.user   # 获取当前登录用户对象
        # 查询当前用户的所有未逻辑删除的地址
        address_qs = Address.objects.filter(is_deleted=False,user=user)
        address_list = []   # 返回给前端的数据列表
        for address in address_qs: # 遍历每一个地址对象
            address_dict = {    #  包装返回前端需展示的地址
                "id": address.id,
                "title": address.title,
                "receiver": address.receiver,
                "province_id": address.province_id,    #  前端点击编辑地址后文本框中显示的数据
                "province": address.province.name,     #  前端页面显示的地址数据
                "city_id": address.city_id,
                "city": address.city.name,
                "district_id": address.district_id,
                "district": address.district.name,
                "place": address.place,
                "mobile": address.mobile,
                "tel": address.tel,
                "email": address.email,
            }
            address_list.append(address_dict)
        context = {
            "addresses":address_list,
            "default_address_id":user.default_address_id
        }

        return render(request,"user_center_site.html",context)

class CreateAddressView(LoginRequiredView):
    """新增收获地址"""
    def post(self,request):
        user = request.user # 获取user对象
        # 查看user对象共有多少个收获地址
        count =  Address.objects.filter(user=user,is_deleted=False).count()
        if count > 20:
            return http.HttpResponseForbidden("用户收货地址上限")
        # json_dict = json.loads(request.body.decode())
        json_dict = json.loads(request.body.decode())  # 请求体非表单数据
        # 获取数据
        title = json_dict.get("title")
        receiver = json_dict.get("receiver")
        province_id = json_dict.get("province_id")
        city_id = json_dict.get("city_id")
        district_id = json_dict.get("district_id")
        place = json_dict.get("place")
        mobile = json_dict.get("mobile")
        tel = json_dict.get("tel")
        email = json_dict.get("email")

        if all([title, receiver, province_id, city_id, district_id, place, mobile]) is False:
            return http.HttpResponseForbidden('缺少必传参数')
        # 校验
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return http.HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return http.HttpResponseForbidden('参数email有误')

        try:  # 往数据库新曾数据
            address = Address.objects.create(
                user=user,
                title=title,
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email
            )
            if user.default_address is None:  # 设置默认地址
                user.default_address = address
                user.save()
        except Address.DoesNotExist:
            return http.HttpResponseForbidden("新增失败")

        address_dict = {  # 数据返回前端渲染出来
            "id":address.id,
            "title":address.title,
            "receiver":address.receiver,
            "province_id":address.province_id,
            "province":address.province.name,
            "city_id":address.city_id,
            "city":address.city.name,
            "district_id":address.district_id,
            "district":address.district.name,
            "place": address.place,
            "mobile":address.mobile,
            "tel":address.tel,
            "email":address.email,
        }
        return http.JsonResponse({"code":RETCODE.OK,"errmsg":"OK","address":address_dict})


class UpdateDestroyAddressView(LoginRequiredView):
    """修改收货地址"""
    def put(self,request,address_id):
        try:
            address = Address.objects.get(id = address_id)
        except Address.DoesNotExist:
            return http.HttpResponseForbidden('要修改的地址不存在')

        json_dict = json.loads(request.body.decode())  # 请求体非表单数据
        # 获取数据
        title = json_dict.get("title")
        receiver = json_dict.get("receiver")
        province_id = json_dict.get("province_id")
        city_id = json_dict.get("city_id")
        district_id = json_dict.get("district_id")
        place = json_dict.get("place")
        mobile = json_dict.get("mobile")
        tel = json_dict.get("tel")
        email = json_dict.get("email")

        if all([title, receiver, province_id, city_id, district_id, place, mobile]) is False:
            return http.HttpResponseForbidden('缺少必传参数')
        # 校验
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return http.HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return http.HttpResponseForbidden('参数email有误')

        try:  # 往数据库新曾数据
            address = Address.objects.filter(id=address_id).update(
                title=title,
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email
            )
        except Address.DoesNotExist:
            return http.HttpResponseForbidden("新增失败")
        address = Address.objects.get(id = address_id)
        address_dict = {  # 数据返回前端渲染出来
            "id": address.id,
            "title": address.title,
            "receiver": address.receiver,
            "province_id": address.province_id,
            "province": address.province.name,
            "city_id": address.city_id,
            "city": address.city.name,
            "district_id": address.district_id,
            "district": address.district.name,
            "place": address.place,
            "mobile": address.mobile,
            "tel": address.tel,
            "email": address.email,
        }
        return http.JsonResponse({"code": RETCODE.OK, "errmsg": "OK", "address": address_dict})

    def delete(self,request,address_id):
        try:
            address = Address.objects.get(id = address_id)
        except Address.DoesNotExist:
            return http.HttpResponseForbidden('要修改的地址不存在')
        address.is_deleted = True
        address.save()
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK'})


class DefaultAddressView(LoginRequiredView):
    """修改默认地址"""
    def put(self,request,address_id):
        try:
            address = Address.objects.get(id=address_id)
        except Address.DoesNotExist:
            return http.HttpResponseForbidden("要修改的地址不存在")
        user = request.user
        user.default_address = address    # 数据修改
        user.save()
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK'})


class UpdateTitleAddressView(LoginRequiredView):
    """修改收货标题"""
    def put(self,request,address_id):
        try:
            address = Address.objects.get(id=address_id)
        except Address.DoesNotExist:
            return http.HttpResponseForbidden("要修改的地址不存在")
        json_dict = json.loads(request.body.decode())   # 请求体非表单数据
        title = json_dict.get('title')    # 获取数据
        address.title = title    # 修改
        address.save()
        return http.JsonResponse({"code":RETCODE.OK,"errmsg":"OK"})


class ChangePasswordView(LoginRequiredView):
    """修改密码"""
    def get(self,request):
        """修改密码界面"""
        return render(request,"user_center_pass.html")


    def post(self,request):
        """修改密码实现"""
        old_password = request.POST.get("old_pwd")   #  获取数据
        new_password = request.POST.get("new_pwd")
        new_password2 = request.POST.get("new_cpwd")

        if not all([old_password,new_password,new_password2]):
            return http.HttpResponseForbidden("缺少必传参数")
        user = request.user   #  获取对象
        #  校验
        if user.check_password(old_password) is False:
            return render(request,"user_center_pass.html",{"origin_pwd_errmsg":"密码错误"})
        if not re.match(r'^[0-9A-Za-z]{8,20}$', new_password):
            return http.HttpResponseForbidden('密码最少8位，最长20位')
        if new_password != new_password2:
            return http.HttpResponseForbidden("两次输入的密码不一致")
        # if new_password == old_password:
        #    return render(request, "user_center_pass.html", {"origin_pwd_errmsg": "密码一样"})
        #     上行为密码一致，未作修改，提示用户
        try:
            user.set_password(new_password)    # 设置新密码
            user.save()
        except Exception:
            return render(request, 'user_center_pass.html', {'change_pwd_errmsg': '修改密码失败'})

        logout(request)    # 状态清除
        response = redirect("/login/")       #  重定向到登录界面
        response.delete_cookie("username")   #   缓存数据清除

        return response


class UserBrowseHistory(View):
    """用户浏览记录"""
    def post(self,request):
        """保存用户浏览记录"""
        # 配置项配置redis三号库保存
        user = request.user   # 获取当前用户
        if not user.is_authenticated:  # 判断用户是否登录，登录为TURE，反之FALSE
            return http.JsonResponse({"code":RETCODE.SESSIONERR,"errmsg":"用户未登录"})
        # 前端以post发送非表单JSON数据
        json_dict = json.loads(request.body.decode())
        sku_id = json_dict.get('sku_id')   # 获取请求体中的数据

        # 校验sku_id
        try:
            sku = SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return http.HttpResponseForbidden('sku_id不存在')

        # 创建连接redis数据库
        conn_redis = get_redis_connection("history")
        pl = conn_redis.pipeline()  # 创建管道执行
        key = 'history_%s' % user.id
        # 去重
        pl.lrem(key,0,sku_id)
        # 将数据存储到列表开头
        pl.lpush(key,sku_id)
        # 截取前5个
        pl.ltrim(key,0,4)
        pl.execute()  # 管道执行

        return http.JsonResponse({'code':RETCODE.OK,'errmsg':'OK'})

    def get(self,request):
        """页面展示浏览记录"""
        conn_redis = get_redis_connection('history')  # 连接redis数据库
        #  获取当前用户在数据库中浏览过的所有sku id
        sku_id_list = conn_redis.lrange('history_%s' % request.user.id,0,-1)
        skus = []  # 用来装每一个sku字典，返回给前端使用
        for sku_id in sku_id_list: # 遍历登录用户的浏览记录列表数据
            sku = SKU.objects.get(id=sku_id)  # 获取每一个SKU id对应商品所有信息
            sku_dict = {     # 提取需要的商品信息，包装成字典
                'id':sku.id,
                'name':sku.name,
                'default_image_url':sku.default_image.url,
                'price':sku.price
            }
            skus.append(sku_dict)
        return http.JsonResponse({"code":RETCODE.OK,"errmsg":"OK","skus":skus})


class UserOrderInfoView(LoginRequiredView):
    """全部订单"""
    def get(self,request,page_num):
        user = request.user  # 用户
        # 查询用户的所有订单信息，并安时间倒序排序
        order_qs = OrderInfo.objects.filter(user=user).order_by('-create_time')

        for order in order_qs: # 遍历得到订单数据中的每一条订单
            # 订单对象新加两条属性（支付方式和订单状态），因为前端需要这两条数据的中文名，
            order.pay_method_name = OrderInfo.PAY_METHOD_CHOICES[order.pay_method -1][1]
            order.status_name = OrderInfo.ORDER_STATUS_CHOICES[order.status - 1][1]

            order.sku_list = [] # 前端页面要使用sku_list
            # 获取每条订单中的所用商品信息，skus为外建关联ordergoods表的别名
            order_goods_qs = order.skus.all()
            for order_goods in order_goods_qs:
                sku = order_goods.sku # 通过外建获取订单商品在SKU中一一对应的sku商品
                sku.count = order_goods.count   #  当前sku的购买数量
                sku.amount = sku.price * sku.count  #  当前sku的总价
                order.sku_list.append(sku)  # 添加

        paginator = Paginator(order_qs, 2)  # 创建分页对象
        page_order = paginator.page(page_num)  # 获取指定页的所有数据，page_num为前端传来
        total_page = paginator.num_pages   # 获取总页数
        # paginator = Paginator(order_qs, 2)
        # # 获取指定页的所有数据
        # page_orders = paginator.page(page_num)
        # # 获取总页数
        # total_page = paginator.num_pages


        # 数据返回
        context = {
            'page_orders': page_order,
            'page_num': page_num,
            'total_page': total_page
        }
        return render(request,'user_center_order.html',context)


class UserFindPassword(View):
    def get(self,request):
        return render(request,'find_password.html')


class UserFindPasswordLogin(View):
    def get(self,request,username):
        text = request.GET.get('text')
        image_code_id = request.GET.get('image_code_id')

        if not all([text,image_code_id]):
            return http.HttpResponseForbidden('缺少必传参数')

        try:
            user = Users.objects.get(username=username)
        except Users.DoesNotExist:
            return http.HttpResponseBadRequest('用户名错误')

        mobile = user.mobile
        redis_conn = get_redis_connection('verify_code')

        image_code = redis_conn.get("img_%s" % image_code_id).decode()

        if text != image_code:
            return http.HttpResponseBadRequest("验证码错误")

        access_token = generate_openid_signature(mobile)

        context = {

            'mobile':mobile,
            'access_token':access_token
        }
        return http.JsonResponse(context)


class UserSendMobile(View):
    def get(self,request):
        access_token = request.GET.get('access_token')
        mobile = check_openid_sign(access_token)
        try:
            user = Users.objects.get(mobile=mobile)
        except Users.DoesNotExist:
            return http.HttpResponseForbidden("手机解密错误")
        redis_conn = get_redis_connection("verify_code")  # 连接数据库
        send_flag = redis_conn.get("send_%s" % mobile)  # 频繁发送短信状态记录
        if send_flag:  # 取到值则表示60s之内有发送过短信，提示用户
            return http.JsonResponse({"code": RETCODE.THROTTLINGERR, "errmsg": "频繁发送短信"})
        sms_code = "%06d" % randint(0, 999999)  # 随机生成6位验证码
        logger.info(sms_code)
        pl = redis_conn.pipeline()
        # 将随机生成的6位验证码保存到数据库中
        # redis_conn.setex("sms_%s" % mobile, 300, sms_code)
        pl.setex("sms_%s" % mobile, 300, sms_code)
        # 手机号发过短信后在redis中存储一个标记
        # redis_conn.setex('send_flag_%s' % mobile, 60, 1)
        pl.setex("send_%s" % mobile, 60, 1)  # 设置60s内同一手机只能发送一条信息，和最开始的判断使用
        pl.execute()
        send_sms_code.delay(mobile, sms_code)
        return http.JsonResponse({'message':'ok'})


class UserMobileCh(View):
    def get(self,request,username):
        user = Users.objects.get(username=username)
        mobile = user.mobile
        sms_code = request.GET.get('sms_code')
        redis_conn = get_redis_connection("verify_code")
        sms_redis_code = redis_conn.get("sms_%s" % mobile).decode()
        if sms_code != sms_redis_code:
            return http.JsonResponse({'status':400,'error':"用户名错误"})
        access_token = generate_openid_signature(mobile)
        content = {
            'user_id':user.id,
            'access_token':access_token
        }
        return http.JsonResponse(content)


class UserMobileResver(View):
    def post(self,request,user_id):
        json_dict = json.loads(request.body.decode())
        password = json_dict.get("password")
        password2 = json_dict.get("password2")
        access_token = json_dict.get('access_token')
        mobile = check_openid_sign(access_token)
        try:
            user = Users.objects.get(id=user_id)
        except Users.DoesNotExist:
            return http.HttpResponseBadRequest('验证错误')

        if not re.match(r'^[0-9A-Za-z]{8,20}$',password):
            return http.HttpResponseBadRequest("请输入8-20位的密码")
        if password != password2:
            return http.HttpResponseBadRequest('密码不一致')

        if mobile != user.mobile:
            return http.HttpResponseBadRequest('access_token不一样')
        user.set_password(password)
        user.save()
        # response = http.JsonResponse({'message':'ok'})
        # response = ('/login/')
        # response.delete_cookie('username')

        return render(request,'login.html')
